/*
* ========== Copyright Header Begin ==========================================
* 
* OpenSPARC T1 Processor File: breakpoint.c
* Copyright (c) 2006 Sun Microsystems, Inc.  All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES.
* 
* The above named program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License version 2 as published by the Free Software Foundation.
* 
* The above named program is distributed in the hope that it will be 
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* General Public License for more details.
* 
* You should have received a copy of the GNU General Public
* License along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
* 
* ========== Copyright Header End ============================================
*/
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"@(#)breakpoint.c	1.8	05/11/17 SMI"

#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <alloca.h>

#include <assert.h>

#include "basics.h"
#include "fatal.h"
#include "allocate.h"
#include "simcore.h"
#include "config.h"
#include "breakpoint.h"
#include "xicache.h"	/* for xicache_instn_flush */


	/*
	 * Breakpoint info is a global structure.
	 * We set a pointer in the cpu structure to it
	 * to indicate that breakpoints are possible for that
	 * cpu. The only thread that can modify this structure
	 * and it's elements is the main run thread ...
	 * .... so we don't need to worry about locking it ....
	 * .... just have to be careful about adding and removing
	 * entries so as to not strand cpus in hyperspace....
	 */

bp_info_t * breakpoint_init()
{
	int i;

	bp_info_t * bip;

	bip = Xcalloc(1,bp_info_t);

	for (i=0; i<BREAK_HASH_SIZE; i++) {
		bip->hash[i] = NULL;
	}

	bip->free_listp = NULL;
	bip->active_listp = NULL;
	bip->active_count = 0;
	bip->disabled_listp = NULL;
	bip->disabled_count = 0;

	bip->next_id = 0;

	bip->do_step = false;

	return bip;
}




	/*
	 * Insert at top of hash ... recently
	 * added breakpoints should be found first ...
	 */

static void bp_hash_insert(bp_info_t * bip, breakpoint_t * bp)
{
	int i;

	assert(bp->enabled);
	assert(bp->hash_nextp == (void*)0);
	assert(bp->free_nextp == (void*)0);
	assert(bp->disabled_nextp == (void*)0);

	i = (bp->pc >> 2) & BREAK_HASH_MASK;

	bp->hash_nextp = bip->hash[i];
	bip->hash[i] = bp;
}



	/* only gets called for existing breakpoints 
	 * which are being deleted or disabled
	 */
static void bp_hash_unhook(bp_info_t * bip, breakpoint_t * bp)
{
	int i;
	breakpoint_t ** pbp, * bb;

	assert(bp->enabled);
	assert(bp->free_nextp == (void*)0);
	assert(bp->disabled_nextp == (void*)0);

	i = (bp->pc >> 2) & BREAK_HASH_MASK;

		/* go find it on list */
	for ( pbp = &(bip->hash[i]); (bb = *pbp)!=(void*)0; pbp=&(bb->hash_nextp)) {
		if (bb == bp) {
			*pbp = bb->hash_nextp;
			bb->hash_nextp = NULL;	/* sanity */
			return;
		}
	}
	abort();
}


static void breaks_changed(bp_info_t * bip)
{
	int i;
	for (i=0; i<simcpu_list.count; i++) {
		simcpu_t *sp;

		sp = LIST_ENTRY(simcpu_list, i);

		xicache_instn_flush(sp);	/* blow away decoded versions ... */
	}
}

	/*
	 * Insert a new breakpoint ...
	 */

breakpoint_t * breakpoint_insert(bp_info_t * bip, tvaddr_t pc, int context)
{
	breakpoint_t * bp;

DBG(	printf("breakpoint_insert: @ 0x%llx, ctxt=0x%x\n", pc, (int)context); );

	if (bip->free_listp != NULL) {
		bp = bip->free_listp;
		bip->free_listp = bp->free_nextp;
		bp->free_nextp = NULL;
	} else {
			/* use calloc to clear all pointer fields */
		bp = (breakpoint_t*)calloc(1, sizeof(*bp));
		if (bp == NULL) fatal("out of memory in breakpoint_insert");
	}

	bp->id = bip->next_id++;
	bp->pc = pc;
	bp->context = context;
	bp->enabled = true;

		/* add to active list */
	bp->active_nextp = bip->active_listp;
	bip->active_listp = bp;
	bip->active_count ++;

		/* must not be disabled */
	assert(bp->disabled_nextp == NULL);
	
	bp_hash_insert(bip, bp);

	breaks_changed(bip);

	return bp;
}

void breakpoint_insert_next(bp_info_t *bip)
{
	bip->do_step = true;
	bip->on_this = false;

	breaks_changed(bip);
}

void breakpoint_clear_next(bp_info_t *bip)
{
	bip->do_step = false;

	breaks_changed(bip);
}

breakpoint_t * breakpoint_find_by_addr(bp_info_t * bip, uint64_t addr, int context)
{
	breakpoint_t * bp;
	int i;

		/* search the bp hash */
	i = (addr >> 2) & BREAK_HASH_MASK;

	for (bp = bip->hash[i]; bp!=NULL; bp = bp->hash_nextp) {
		if (bp->pc == addr && bp->context == context) return bp;
	}

	return (breakpoint_t*)0;
}


int breakpoint_delete_by_id(bp_info_t * bip, int id)
{
	breakpoint_t **pbp, * bp;

		/* search first active list */

	for (pbp = &(bip->active_listp); (bp= *pbp) != NULL; pbp=&(bp->active_nextp)) {
		assert(bp->enabled);	/* sanity */
		if (bp->id == id) {
				/* hit on active list */
				/* unhook from list and unhook from hash */
			bp_hash_unhook(bip, bp);

			*pbp = bp->active_nextp;
			bip->active_count --;
			bp->enabled = false;

			goto delete_me;
		}
	}
	
		/* then search disabled list */

	for (pbp = &(bip->disabled_listp); (bp= *pbp) != NULL; pbp=&(bp->disabled_nextp)) {
		assert(!bp->enabled);	/* sanity */
		if (bp->id == id) {
				/* hit on disabled list */
			*pbp = bp->disabled_nextp;
			bip->disabled_count --;

			goto delete_me;
		}
	}
	
	return -1;	/* no such id */

delete_me:

		/* just stuff back on free list */
	bp->active_nextp = NULL;
	bp->disabled_nextp = NULL;
	assert(bp->free_nextp == NULL);
	assert(bp->hash_nextp == NULL);

	bp->free_nextp = bip->free_listp;
	bip->free_listp = bp;

	breaks_changed(bip);

	return 0;
}




int breakpoint_delete_by_addr(bp_info_t * bip, uint64_t addr, int context)
{
	bool_t flag_deleted = false;
	breakpoint_t * bp;
	while ((bp = breakpoint_find_by_addr(bip, addr, context))!=(breakpoint_t *)0) {
		breakpoint_delete_by_id(bip, bp->id);
		flag_deleted = true;
	}

	return flag_deleted;
}




	/*
	 * Either the bp is enabled, disabled or doesn't exist !
	 */

int breakpoint_disable_by_id(bp_info_t * bip, int id)
{
	breakpoint_t **pbp, * bp;

		/* search first active list */

	for (pbp = &(bip->active_listp); (bp= *pbp) != NULL; pbp=&(bp->active_nextp)) {
		assert(bp->enabled);	/* sanity */
		if (bp->id == id) {
				/* hit on active list */
				/* unhook from active list and unhook from hash */
			bp_hash_unhook(bip, bp);

			*pbp = bp->active_nextp;
			bip->active_count --;
			bp->enabled = false;

			goto disable_me;
		}
	}
	
		/* then search disabled list */

	for (bp = bip->disabled_listp; bp != NULL; bp=bp->disabled_nextp) {
		assert(!bp->enabled);	/* sanity */
		if (bp->id == id) {
				/* hit on disabled list */
			printf("Breakpoint %d is already disabled\n", bp->id);
			return 0;	/* job done */
		}
	}
	
	printf("No breakpoint with id %d\n", bp->id);
	return -1;	/* no such id */

disable_me:
		/* mark disabled */
	bp->active_nextp = NULL;
	assert(bp->disabled_nextp == NULL);
	assert(bp->free_nextp == NULL);
	assert(bp->hash_nextp == NULL);

	bp->disabled_nextp = bip->disabled_listp;
	bip->disabled_listp = bp;
	bip->disabled_count ++;

	breaks_changed(bip);

	return 0;
}



int breakpoint_enable_by_id(bp_info_t * bip, int id)
{
	breakpoint_t **pbp, * bp;

		/* make sure not already enabled */
	for (bp = bip->active_listp; bp != NULL; bp=bp->active_nextp) {
		assert(bp->enabled);	/* sanity */
		if (bp->id == id) {
			printf("Breakpoint %d is already enabled\n", bp->id);
			return 0;	/* job done */
		}
	}
	
		/* then search disabled list */

	for (pbp = &(bip->disabled_listp); (bp = *pbp) != NULL; pbp=&(bp->disabled_nextp)) {
		assert(!bp->enabled);	/* sanity */
		if (bp->id == id) {
			*pbp = bp->disabled_nextp;
			bp->disabled_nextp = NULL;
			bip->disabled_count --;
			goto enable_me;
		}
	}
	
	printf("No breakpoint with id %d\n", bp->id);
	return -1;	/* no such id */

enable_me:
	assert(bp->active_nextp == NULL);
	assert(bp->disabled_nextp == NULL);
	assert(bp->free_nextp == NULL);
	assert(bp->hash_nextp == NULL);
	assert(!bp->enabled);

	bp->enabled = true;

	bp->active_nextp = bip->active_listp;
	bip->active_listp = bp;
	bip->active_count ++;

	bp_hash_insert(bip, bp);

	breaks_changed(bip);	/* no need to flush xcache here, but do set ptrs */

	return 0;
}




	/*
	 * This is what gets called if we're checking for a possible breakpoint hit.
	 * We return one of three options:
	 * a) NOTHING		- didn't hit anything
	 * b) ON_BREAKPOINT	- we landed on a breakpoint
	 */
breakpoint_check_code_t breakpoint_check(bp_info_t * bip, uint64_t pc, int context, breakpoint_t **bpp)
{
	breakpoint_t * bp;
	int i;

	if (bip->do_step) {
		if (!bip->on_this) {
			bip->on_this = true;
		} else {
			*bpp = NULL;
			return ON_BREAKPOINT;
		}
	}

		/* search the bp hash */
	i = (pc >> 2) & BREAK_HASH_MASK;

	for (bp = bip->hash[i]; bp!=NULL; bp = bp->hash_nextp) {
		if (!bp->enabled) continue;	/* in process of being deleted */
		if (bp->pc == pc) {
DBG(			printf("Matched pc @ 0x%llx : current ctxt = 0x%x, match ctxt = 0x%x\n",
				pc, context, bp->context ); );
			if (bp->context == context) {
					/* bingo ! */
				*bpp = bp;
				return ON_BREAKPOINT;
			}
		}
	}

	return NOTHING;
}

bool_t
breakpoint_any_reached(void)
{
	int i;

	for (i = 0; i < simcpu_list.count; i++) {
		simcpu_t *sp;
		breakpoint_t *bp;

		sp = LIST_ENTRY(simcpu_list, i);

		if (sp->bp_infop != NULL
		    && breakpoint_check(sp->bp_infop, sp->pc,
			DEFAULT_BP_CONTEXT , &bp)
		    == ON_BREAKPOINT) {
			return (true);
		}
	}

	return (false);
}



/*
 * Dump all the breakpoints (in order)
 */
bool_t
breakpoint_dump(bp_info_t* bg, FILE* fp)
{
	abort();
	return false;
}

bp_info_t *
breakpoint_restore(FILE* fp)
{
	abort();
	return false;
}

int post_raw_dump_breakpoint_info_t (char *dir, const char *filename, bp_info_t *rp) {return 0;}
int post_raw_restore_breakpoint_info_t (char *dir, const char *filename, bp_info_t* rp) {return 0;}





